﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using Microsoft.Practices.Unity;
using Wyn.PMS.Common.SecurityControl;
using Wyn.PMS.Common.Utils;
using Wyn.PMS.Entity.Entities;
using Wyn.PMS.Entity.IDataAccess;

namespace Wyn.PMS.Common.NightAudit
{
    /// <summary>
    /// 执行夜审
    /// <para>--------------------------</para>
    /// <para>@Author: cnalexi@gmail.com</para> 
    /// <para>@Create: 2012/5/3</para>  
    /// </summary>
    public class DoNightAudit
    {

        private static readonly IUnityContainer container = UnityContainerService.GetContainer();

        /// <summary>
        /// 夜审
        /// </summary>
        public void Audit(HotelBizDay bizDay)
        {
            #region 初始化

            IHotelBizDayRepository bizDayRepository = container.Resolve<IHotelBizDayRepository>();

            IOrderRepository orderRepository = container.Resolve<IOrderRepository>();

            IAccountRepository accountRepository = container.Resolve<IAccountRepository>();

            IAccountRootRepository accountRootRepository = container.Resolve<IAccountRootRepository>();

            IRoomTypeRepository roomTypeRepository = container.Resolve<IRoomTypeRepository>();

            var transactionOptions = new System.Transactions.TransactionOptions();

            transactionOptions.IsolationLevel = System.Transactions.IsolationLevel.ReadUncommitted;

            transactionOptions.Timeout = new TimeSpan(0, 2, 30);

            #endregion

            try
            {
                #region step 1 建立临时工作池检查数据 Create Worksapce

                //后期加入内存缓冲池夜审数据公布与核对处理
                
                //☆风险☆ 当夜审异常之后可能出现多人点击开始夜审的按钮！需控制
                if (bizDay.Status!=EnumHelper.GetCode(NightAuditCode.Status.E)
                || (bizDay.Status == EnumHelper.GetCode(NightAuditCode.Status.E)
                    && bizDay.Steping == (int)NightAuditCode.Steping.ReadyCheck))
                {
                    bizDay.Steping = (int) NightAuditCode.Steping.ReadyCheck;

                    bizDay.Status = EnumHelper.GetCode(NightAuditCode.Status.S);

                    bizDay.AuditTime = DateTime.Now;

                    bizDayRepository.Update(bizDay);//更新状态
                }
                
                string hotelId = bizDay.HotelID;

                DateTime todayBizDay = bizDay.BizDay;

                string inStatus = EnumHelper.GetCode(OrderStatus.I);

                string outStatus = EnumHelper.GetCode(OrderStatus.O);

                IList<RoomType> roomTypes;

                using (var scope = new System.Transactions.TransactionScope(System.Transactions.TransactionScopeOption.Required, transactionOptions))
                {
                    roomTypes = roomTypeRepository.BatchFind(hotelId, r => r.HotelID == hotelId && r.IsValid == true);
                    scope.Complete();
                }

                //☆条件：在住和今日结账走人的接待单
                Expression<Func<Order, bool>> orderExp = o =>o.HotelID == hotelId && (o.Status == inStatus || (o.Status ==outStatus && o.CheckOutBizDay==todayBizDay));

                IList<string> path = new List<string>{typeof (PriceDetail).Name};

                IList<Order> allOrders; 

                using (var scope = new System.Transactions.TransactionScope(System.Transactions.TransactionScopeOption.Required, transactionOptions))
                {
                    //重点修改
                    allOrders = orderRepository.BatchFind(bizDay.HotelID, orderExp);
                    
                    scope.Complete();
                }

                //装载PRICEs
                allOrders= BuildOrderPrices(allOrders, hotelId, inStatus, outStatus, todayBizDay,transactionOptions);
                
                //获取在住单
                var inOrders = allOrders.Where(o=>o.Status==inStatus).ToList();

               
                //DateTime toDayMax = todayBizDay.AddDays(1).AddSeconds(-1);
                //var inOrders = allOrders.Any() ? allOrders.Where(o => o.Status == inStatus && o.CheckinTime <= toDayMax).ToList() : null;
                
                // ☆ ---- 在住单复审检查 营业日 和 住单 [时租，白日，续住等等]  ---- ☆
                //var canntAudits = Precheck.CheckAuditOrders(inOrders,bizDay);

                //if (canntAudits != null && canntAudits.Any())
                //{
                //    throw new Exception("有未处理在住客人,不能进入夜审");
                //}
               
                #endregion

                #region step 2 滚租金 Rent Roll

                if (bizDay.Status != EnumHelper.GetCode(NightAuditCode.Status.E))
                {
                    bizDay.Steping = (int)NightAuditCode.Steping.RentRoll;
                }

                RentRoll rentRoll = new RentRoll();

                //按房型统计需要滚租的房价累积值
                IDictionary<string,decimal> rollRoomPrice=null;

                if (inOrders.Count > 0)
                {
                    rollRoomPrice = rentRoll.Roll(bizDay, inOrders, roomTypes);
                }
                else
                {
                    if (bizDay.Status == EnumHelper.GetCode(NightAuditCode.Status.E)
                    && bizDay.Steping == (int)NightAuditCode.Steping.RentRoll)
                    {
                        bizDay.Status = EnumHelper.GetCode(NightAuditCode.Status.I);
                    }
                }

                #endregion

                #region step 3 科目汇总 Ledger Total 

                if (bizDay.Status != EnumHelper.GetCode(NightAuditCode.Status.E))
                {
                    bizDay.Steping = (int)NightAuditCode.Steping.LedgerTotal;
                }

                string asX=EnumHelper.GetCode(AccountStatus.X);//废账不进统计
                
                //只做营业日等于今天的账 非冲 非废 非滚夜审(2B状态去掉)
                Expression<Func<Account, bool>> accountExp =
                    r => r.HotelID == hotelId
                         && r.BizDay== todayBizDay
                         && r.Status != asX
                         && r.IsStrike == false;

                IList<Account> notRollAccounts;

                using (var scope = new System.Transactions.TransactionScope(System.Transactions.TransactionScopeOption.Required, transactionOptions))
                {
                    //今日 产生 未过租的子账单
                    notRollAccounts = accountRepository.BatchFind(hotelId, accountExp);

                    scope.Complete();
                }

                LedgerTotal ledgerTotal = new LedgerTotal();
                
                //非冲 非滚账 进 科目汇总
                ledgerTotal.Total(bizDay, notRollAccounts);
                
                #endregion

                #region step 4 客账结余 Account Remain

                if (bizDay.Status != EnumHelper.GetCode(NightAuditCode.Status.E))
                {
                    bizDay.Steping = (int)NightAuditCode.Steping.AccountRemain;
                }
                
                //在住和临时挂账(=迟付)
                string ars_I = EnumHelper.GetCode(AccountStatus.I), ars_S = EnumHelper.GetCode(AccountStatus.S);

                //主账标记
                string art_RA = EnumHelper.GetCode(AccountRootType.RA);

                //房账的 在住，迟付 TODO:5个固定账不计算客账结余
                Expression<Func<AccountRoot, bool>> accRootExp =
                    r => r.HotelID == hotelId
                         && r.AccountRootType == art_RA
                         && (r.Status == ars_I || r.Status == ars_S);

                IList<string> arPath = new List<string>{ typeof(Account).Name };

                IList<AccountRoot> accountRoots;

                //签字挂账（迟付)的接待单
                IList<Order> signOrders = new List<Order>();

                using (var scope = new System.Transactions.TransactionScope(System.Transactions.TransactionScopeOption.Required, transactionOptions))
                {
                    accountRoots = accountRootRepository.BatchIncludeFind(hotelId, accRootExp, arPath);

                    var sAccountRoots = accountRoots.Where(a => a.Status == ars_S).ToList();

                    if (sAccountRoots.Count > 0)
                    {
                        var signOrderIds = sAccountRoots.Select(a => a.AccountRootID);

                        signOrders = orderRepository.BatchIncludeFind(bizDay.HotelID, o => signOrderIds.Contains(o.OrderID), path).ToList();
                    }
                    
                    scope.Complete();
                }

                accountRoots = BuildIncludeInfo(accountRoots, allOrders,signOrders);

                if (accountRoots != null && accountRoots.Count>0)
                {
                    AccountRemain accountRemain = new AccountRemain();
                    accountRemain.CalculateRemain(bizDay, accountRoots, notRollAccounts);
                }
                else
                {
                    if (bizDay.Status == EnumHelper.GetCode(NightAuditCode.Status.E)
                    && bizDay.Steping == (int)NightAuditCode.Steping.AccountRemain)
                    {
                        bizDay.Status = EnumHelper.GetCode(NightAuditCode.Status.I);
                    }
                }

                #endregion

                #region step 5 酒店营业状态 Operation Status

                if (bizDay.Status != EnumHelper.GetCode(NightAuditCode.Status.E))
                {
                    bizDay.Steping = (int)NightAuditCode.Steping.OperationStatus;
                }

                OperationStatus operationStatus = new OperationStatus();

                IList<NaOperationStatus> states = null;
                
                //注意 当过夜审有问题时，再把 RA房账过滤出来
                //按房型统计客房收入总值 ，（注意：迟付可以入账，所以需要被统计进客房总收入）
                IList<Order> rollAccountOrders = GetRollAccountOrders(notRollAccounts, allOrders, orderRepository, hotelId);

                IDictionary<string, decimal> rtRoomFeeAccounts = SumRoomFeeAccounts(notRollAccounts, rollAccountOrders, roomTypes);
                
                //计算NoShow
                states = operationStatus.Snapshot(bizDay, roomTypes,notRollAccounts,rollRoomPrice, rtRoomFeeAccounts,rollAccountOrders,allOrders);
                
                #endregion
                
                #region step 6 经营项目分析 Operation Analysis

                if (bizDay.Status != EnumHelper.GetCode(NightAuditCode.Status.E))
                {
                    bizDay.Steping = (int)NightAuditCode.Steping.OperationAnalysis;
                }
                
                OperationAnalysis operationAnalysis = new OperationAnalysis();
                
                if (states!=null)
                {
                    operationAnalysis.Total(bizDay, rtRoomFeeAccounts, notRollAccounts, roomTypes, states, allOrders.ToList(), rollAccountOrders);
                }
                else
                {
                    if (bizDay.Status == EnumHelper.GetCode(NightAuditCode.Status.E)
                    && bizDay.Steping == (int)NightAuditCode.Steping.OperationAnalysis)
                    {
                        bizDay.Status = EnumHelper.GetCode(NightAuditCode.Status.I);
                    }
                }

                #endregion

                #region step 7 数据转结清理完成夜审 -A 标记房账  Complete Audit

                bizDay.Steping = (int)NightAuditCode.Steping.CompleteAudit_A;
                
                CompleteAudit completeAudit = new CompleteAudit();

                //TRY 进行排错处理 等等

                completeAudit.Complete(notRollAccounts,bizDay);
                
                #endregion

                #region step 8 数据转结清理完成夜审 -B 标记脏房  Complete Audit

                bizDay.Steping = (int)NightAuditCode.Steping.CompleteAudit_B;

                completeAudit.Complete(inOrders, bizDay);

                #endregion

                #region step 9 数据转结清理完成夜审 -C 更新营业日  Complete Audit

                bizDay.Steping = (int)NightAuditCode.Steping.CompleteAudit_C;

                completeAudit.Complete(bizDay);

                #endregion
            }
            catch (Exception ex)
            {
                bizDay.Status = EnumHelper.GetCode(NightAuditCode.Status.E);

                bizDayRepository.Update(bizDay);

                string exInfo = ex.InnerException!=null ? ex.InnerException.Message : ex.Message;

                string msg = string.Format("夜审步骤{0}发生异常:{1}", bizDay.Steping, exInfo);

                OperateLog.InfoLog(new LogMessage(LogCategory.NightAuditException, GlobalConst.NIGHT_AUDIT_SYSTEM, bizDay.HotelID, string.Empty, bizDay.Steping.ToString(), true, bizDay.HotelID, msg));
            }
            
        }

        private IList<Order> BuildOrderPrices(IList<Order> allOrders, string hotelId, string inStatus, string outStatus, DateTime todayBizDay,System.Transactions.TransactionOptions transactionOptions)
        {
            IList<PriceDetail> priceDetails = new List<PriceDetail>();

            IPriceDetailRepository priceDetailRepos = container.Resolve<IPriceDetailRepository>();

            StringBuilder sb = new StringBuilder();
            sb.Append(" SELECT [BizDay],[ID],[Price],[NormalPrice],p.[OrderID],p.[HotelID] FROM [PriceDetail] as p");
            sb.Append(" INNER JOIN [Order] as o ON p.OrderID=o.OrderID");
            sb.AppendFormat(" WHERE (o.[Status]='{0}' OR (o.[Status]='{1}' AND o.CheckOutBizDay='{2}'))", inStatus, outStatus, todayBizDay.ToShortDateString());
            sb.AppendFormat(" AND o.HotelID='{0}' AND p.BizDay='{1}'", hotelId, todayBizDay.ToShortDateString());
            var querySql = sb.ToString();

            using (var scope = new System.Transactions.TransactionScope(System.Transactions.TransactionScopeOption.Required, transactionOptions))
            {
                priceDetails = priceDetailRepos.FindOrderPrices(hotelId, querySql);

                scope.Complete();
            }
            System.Threading.Tasks.Parallel.ForEach(allOrders, order =>
                {
                    if (priceDetails != null && priceDetails.Any())
                    {
                        var priceList = priceDetails.Where(o => o.OrderID == order.OrderID).ToList();
                        order.PriceDetail = priceList;
                    }
                    else
                    {
                        order.PriceDetail = new List<PriceDetail>();
                    }
                });
            return allOrders;
        }
        
        private IList<Order> GetRollAccountOrders(IList<Account> notRollAccounts, IList<Order> allOrders, IOrderRepository orderRepository,string hotelId)
        {
            var transactionOptions = new System.Transactions.TransactionOptions();

            transactionOptions.IsolationLevel = System.Transactions.IsolationLevel.ReadUncommitted;
            //获取去重的OrderIdS
            var notRollOrderIds= notRollAccounts.Select(o => o.CreateOrderID).Distinct();

            IList<Order> rollRoomTypeOrders = new List<Order>();

            IList<string> otherOrderIds = new List<string>();

            foreach (var orderId in notRollOrderIds)
            {
                var order = allOrders.FirstOrDefault(o => o.OrderID == orderId);

                if (order == null)
                {
                    otherOrderIds.Add(orderId);
                }
                else
                {
                    rollRoomTypeOrders.Add(order);
                }
            }

            if (otherOrderIds.Any())
            {
                IList<Order> otherOrders = new List<Order>();

                using (var scope = new System.Transactions.TransactionScope(System.Transactions.TransactionScopeOption.Required, transactionOptions))
                {
                    otherOrders = orderRepository.BatchFind(hotelId, o => otherOrderIds.Contains(o.OrderID));

                    scope.Complete();
                }

                rollRoomTypeOrders = rollRoomTypeOrders.Union(otherOrders).ToList();
            }

            return rollRoomTypeOrders;
        }

        private IList<AccountRoot> BuildIncludeInfo(IList<AccountRoot> accountRoots, IList<Order> allOrders, IList<Order> signOrders)
        {
            foreach (var art in accountRoots)
            {
                art.Order = allOrders.FirstOrDefault(o => o.OrderID == art.AccountRootID);
                
                if (art.Order == null) art.Order = signOrders.FirstOrDefault(o => o.OrderID == art.AccountRootID);
            }
            return accountRoots;
        }

        private IDictionary<string, decimal> SumRoomFeeAccounts(IList<Account> notRollAccounts, IList<Order> rollRoomTypeOrders, IList<RoomType> roomTypes)
        {
            IDictionary<string, decimal> rtRoomFee = new Dictionary<string, decimal>();

            string CIT_HR = EnumHelper.GetCode(CheckInType.HR), CIT_DR = EnumHelper.GetCode(CheckInType.DR);

            foreach (var roomtype in roomTypes)
            {
                //旧版里使用此方法过滤NS房费 && o.CheckInType != EnumHelper.GetCode(CheckInType.HR) && o.CheckInType != EnumHelper.GetCode(CheckInType.DR)
                string roomTypeId = roomtype.RoomTypeID;

                IEnumerable<string> roomTypeOrderIds = rollRoomTypeOrders.Where(o => o.RoomTypeID == roomTypeId).Select(o => o.OrderID);

                decimal receipts = notRollAccounts
                    .Where(a => roomTypeOrderIds.Contains(a.CreateOrderID)
                                && !a.IsStrike
                                && a.CheckInType != CIT_HR
                                && a.CheckInType != CIT_DR
                                && (a.LedgerCode == LedgerCode.标准房租
                                   || a.LedgerCode == LedgerCode.加床))
                    .Sum(o => o.Balance);

                rtRoomFee.Add(roomTypeId, receipts);
            }
            
            return rtRoomFee;
        }
        
    }
}
